package com.llw.sunnyweather.logic

import com.llw.sunnyweather.logic.dao.PlaceDao
import com.llw.sunnyweather.logic.model.DailyResponse
import com.llw.sunnyweather.logic.model.Place
import com.llw.sunnyweather.logic.model.Weather
import com.llw.sunnyweather.logic.network.SunnyWeatherNetwork
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import java.lang.Exception
import java.lang.RuntimeException
import kotlin.coroutines.CoroutineContext
import androidx.lifecycle.liveData as liveData

/**
 * 仓库层的同一封装入口
 */
object Repository {

    /**
     * liveData()函数时lifecycle-livedata-ktx库提供的一个非常强大且好用的功能，它可以自动构建并返回一个LiveData对象，然后
     * 在它的代码块中提供一个挂起函数的上下文，这样我们就可以在liveData()函数的代码块中调用任意的挂起函数了。这里调用了
     * SunnyWeatherNetwork的searchPlaces()来搜索城市数据，然后判断如果服务器响应的状态是ok，那么就使用Kotlin内置的Result.success()
     * 方法来包装获取的城市列表数据，否则使用Result.failure()方法来包装一个异常信息。最后使用一个emit()方法来通知数据变化，只不过这里
     * 我们无法直接取得返回的LiveData对象，所以lifecycle-livedata-ktx库提供了这样一个替代方法。
     *
     * 另外需要注意，下面的代码中我们还将liveData()函数的线程参数类型指定成了Dispatchers.IO，这样代码块中的所有代码就都能运行在子线程中了。
     * 总所周知，Android是不允许在主线程中进行网络请求的，诸如读写数据库之类的本地数据操作也不建议在主线程中进行。因此非常有必要在仓库层进行
     * 一次线程转换。
     */
    fun searchPlace(query: String) = fire(Dispatchers.IO) {
        val placeResponse = SunnyWeatherNetwork.searchPlaces(query)
        if (placeResponse.status == "ok") {
            val place = placeResponse.places
            //包装获取的城市列表数据
            Result.success(place)
        } else {
            //包装一个异常信息
            Result.failure(RuntimeException("response status is ${placeResponse.status}"))
        }
    }


    fun refreshWeather(lng: String, lat: String) = fire(Dispatchers.IO) {
        coroutineScope {
            val deferredRealtime = async {
                SunnyWeatherNetwork.getRealtimeWeather(lng, lat)
            }
            val deferredDaily = async {
                SunnyWeatherNetwork.getDailyWeather(lng, lat)
            }

            val realtimeResponse = deferredRealtime.await()
            val dailyResponse = deferredDaily.await()
            if (realtimeResponse.status == "ok" && dailyResponse.status == "ok") {
                val weather = Weather(
                    realtimeResponse.result.realtime,
                    dailyResponse.result.daily
                )
                Result.success(weather)
            } else {
                Result.failure(
                    RuntimeException(
                        "realtime response status is ${realtimeResponse.status}" +
                                "daily response status is ${dailyResponse.status}"
                    )
                )
            }
        }
    }

    fun savePlace(place: Place) = PlaceDao.savePlace(place)

    fun getSavedPlace() = PlaceDao.getSavedPlace()

    fun isPlaceSaved() = PlaceDao.isPlaceSaved()

    private fun <T> fire(context: CoroutineContext, block: suspend () -> Result<T>) =
        liveData<Result<T>>(context) {
            val result = try {
                block()
            } catch (e: Exception) {
                Result.failure<T>(e)
            }
            emit(result)
        }


}